package cn.redragon.soa.security;

import cn.redragon.soa.common.core.util.jwt.RsaJwtTokenVerifier;
import cn.redragon.soa.common.web.config.db.DataSourceHolder;
import cn.redragon.soa.common.web.config.security.keycloak.KeycloakBasicAuthenticationProvider;
import cn.redragon.soa.common.web.config.security.keycloak.KeycloakJwtAuthConverter;
import cn.redragon.soa.common.web.config.security.keycloak.KeycloakSpringBootProperties;
import cn.redragon.soa.common.web.config.web.CommonRequestLoggingFilter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

/**
 * abstract class of Web Security config
 * <p>
 * The sub classes should extend it and annotated with :
 * <p>
 * \@Configuration
 * \@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
 * <p>
 * the ignorePaths could be override to add external URIs that not processed by security
 * <p>
 * The jwtRequestMatcher should be override to provide the RequestMatcher that process with self-signature JWT token,
 * other requests will process authentication with Keycloak
 */
@Slf4j
public abstract class CommonSecurityConfig {

    @Value("${security.csp.headers:*}")
    protected String contentSecurityPolicy;

    @Autowired(required = false)
    private DataSourceHolder dataSourceHolder;

    @Autowired
    private KeycloakSpringBootProperties keycloakProperties;

    @Autowired
    private KeycloakBasicAuthenticationProvider keycloakBasicAuthenticationProvider;

    @Autowired(required = false)
    private RsaJwtTokenVerifier tokenVerifier;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors().and()
            .csrf().disable()
            .authorizeHttpRequests()
            .antMatchers(Iterables.toArray(ignorePaths(), String.class)).permitAll()
            .anyRequest().authenticated()
            .and()
            .httpBasic()
            // customize response of exception
            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));

        http.addFilterBefore(new CommonRequestLoggingFilter(dataSourceHolder), BasicAuthenticationFilter.class);

        http.oauth2ResourceServer()
            .jwt()
            .decoder(customJwtDecoder())
            .jwtAuthenticationConverter(keycloakJwtAuthConverter())
            .and().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .headers().httpStrictTransportSecurity().includeSubDomains(true).maxAgeInSeconds(31536000)
            .and().contentSecurityPolicy(contentSecurityPolicy);

        return http.build();
    }

    /**
     * @return KeycloakJwtAuthConverter
     */
    @Bean
    public KeycloakJwtAuthConverter keycloakJwtAuthConverter() {
        KeycloakJwtAuthConverter converter = new KeycloakJwtAuthConverter(keycloakProperties);
        JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
        // remove SCOPE_ prefix
        authoritiesConverter.setAuthorityPrefix("");
        converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
        //converter.setPrincipalClaimName(JwtClaimNames.SUB);
        return converter;
    }

    /**
     * @return JwtDecoder
     */
    public JwtDecoder customJwtDecoder() {
        return NimbusJwtDecoder.withPublicKey((RSAPublicKey) tokenVerifier.getPublicKey()).build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers(Iterables.toArray(ignorePaths(), String.class));
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class).authenticationProvider(keycloakBasicAuthenticationProvider).build();
    }

    protected List<String> ignorePaths() {
        return Lists.newArrayList("/swagger-ui/**", "/swagger-ui.html", "/v*/api-docs/**", "/manage/health/**");
    }
}
